package com.designpatterns.proxy.myproxy;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class MyProxy {

    private static String LN = "\r\n";


    public static Object newProxyInstance(MyClassLoader myClassloader, Class<?>[] interfaces, MyInvocationHandler h) {
        System.out.println();
        FileWriter out = null;
        File f = null;
        try {

            //1.生成源代码
            String src = generateSrc(interfaces[0]);

            //2.保存到本地位置
            String basePath =ClassLoader.getSystemResource("").getPath()+"$Proxy0.java";
            f = new File(basePath);
            out = new FileWriter(f);
            out.write(src);
            out.close();
            //3.编译
            // 获取编译器实例
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

            // 获取标准文件管理器实例
            StandardJavaFileManager manager = compiler.getStandardFileManager(null,null,null);

            // 获取要编译的编译单元
            Iterable<? extends JavaFileObject> compilationUnits = manager.getJavaFileObjects(f);

            // 编译选项，在编译java文件时，编译程序会自动的去寻找java文件引用的其他的java源文件或者class。 -sourcepath选项就是定义java源文件的查找目录，
            // -classpath选项就是定义class文件的查找目录
            //Iterable<String> options = Arrays.asList("-encoding", encoding, "-classpath", jars, "-d", targetDir,
            // "-sourcepath", sourceDir);


            CompilationTask compilationTask = compiler.getTask(null, manager, null, null, null,
                    compilationUnits);
            // 运行编译任务
            compilationTask.call();

            manager.close();


            //4.动态load到JVM
            Class proxyClass = myClassloader.findClass("$Proxy0");

            //5.返回被代理后的代理对象
            Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);

            //拿到构造方法
            //1.从调用的构造函数参数来说，Class.newInstance只能调用无参构造函数，Constructor.newInstance则无此限制，原因通过Class类的getDeclaredConstructor(Class<?>... parameterTypes)方法就可以知道
            //2.从调用的构造函数的可视性来说，Class.newInstance只能调用public类型的构造函数(不能调用内部类，会抛出java.lang.ClassNotFoundException异常)，Constructor.newInstance在某些情况下可以调用private类型的构造函数
            return constructor.newInstance(h);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            f.delete();
        }

        return null;
    }

    private static  String generateSrc(Class<?> interfaces){
        //Class cls = interfaces.

        StringBuffer str = new StringBuffer();
        //str.append("package com.designpatterns.proxy.myproxy;").append(LN);
        str.append("import java.lang.reflect.Method;").append(LN);
        str.append("import com.designpatterns.proxy.myproxy.MyInvocationHandler;").append(LN);
        str.append("import com.designpatterns.proxy.myproxy.MyProxy;").append(LN);
        str.append(LN);
        str.append("public class $Proxy0 implements ").append(interfaces.getName()).append(" {").append(LN);


        str.append("    private MyInvocationHandler h;").append(LN);
        str.append("    public $Proxy0(MyInvocationHandler h) { ").append(LN);
        str.append("        this.h = h;").append(LN);

        str.append("    }").append(LN);

        //循环接口方法
        for(Method m : interfaces.getMethods()) {
            str.append("    public ").append(m.getReturnType()).append(" ").append(m.getName()).append("() { ")
                    .append(LN);
            str.append("        try {").append(LN);
            //通过反射调用
            str.append("            Method m =").append(interfaces.getName()).append(".class.getMethod(\"").append(m
                    .getName()).append("\", new Class[]{});").append(LN);
            str.append("            this.h.invoke(this, m, null);").append(LN);
            str.append("        } catch(Throwable e) {").append(LN);
            str.append("            e.printStackTrace();").append(LN);
            str.append("        }").append(LN);
            str.append("    }").append(LN);
        }

        str.append("}");
        //System.out.println(str.toString());
        return str.toString();
    }


}
